package com.modbus.handler;

import com.modbus.constant.ModbusConstants;
import com.modbus.util.CRC16;
import com.modbus.util.HexUtil;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * @author heyayun
 * @description TODO
 * @date 2021/5/24 16:58
 */
@Slf4j
public class ModbusFieldBasedFrameDecoder extends ByteToMessageDecoder {
    /**
     * Decode the from one {@link ByteBuf} to an other. This method will be called till either the input
     * {@link ByteBuf} has nothing to read when return from this method or till nothing was read from the input
     * {@link ByteBuf}.
     *
     * @param ctx the {@link ChannelHandlerContext} which this {@link ByteToMessageDecoder} belongs to
     * @param in  the {@link ByteBuf} from which to read data
     * @param out the {@link List} to which decoded messages should be added
     * @throws Exception is thrown if an error occurs
     */
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        {
            // 数据转换
            byte[] bytes = new byte[in.readableBytes()];
            in.getBytes(in.readerIndex(), bytes);
            String hexId = HexUtil.byte2HexStr(bytes);
            log.info("接收数据：{}", hexId);
        }
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    protected Object decode(
            @SuppressWarnings("UnusedParameters") ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        // <---------------------  数据校验  -------------------->
        int length = in.readableBytes();
        if(length < ModbusConstants.NUM_5) {
            log.warn("初始长度不够等待数据");
            return null;
        }
        // ByteBuf数据转换成byte数组
        byte[] bytes = new byte[length];
        in.getBytes(in.readerIndex(), bytes);
        // 数据长度
        int frameLength = frameLength(bytes);
        // 0 - 协议校验码有误
        if (frameLength == 0) {
            return in.readUnsignedByte();
        }
        // 数据校验
        int flag =  crc16(bytes, frameLength);
        // 1 - 长度不够等待数据包
        if (flag == 1) {
            log.warn("解析后长度不够等待数据");
            return null;
        }
        // 0 - 校验错误丢掉一位继续解析
        if (flag == 0) {
            return in.readUnsignedByte();
        }
        // <---------------------  数据解析  -------------------->
        return in.readRetainedSlice(frameLength);
    }

    /**
     * 数据合法性校验
     *
     * 目前支持
     *       0x01: 读线圈寄存器
     *       0x03: 读保持寄存器
     *       0x05: 写单个线圈寄存器
     *
     * 0 - 校验错误
     * 1 - 长度不够
     * 2 - 正常
     * @param bytes 数据
     * @return
     */
    private int frameLength(byte[] bytes) {
        int frameLength = 0;
        // 功能码
        short protocolIdentifier = bytes[1];
        switch (protocolIdentifier) {
            case ModbusConstants.RtuFunction.CODE_1:
            case ModbusConstants.RtuFunction.CODE_3:
                frameLength = frameLength3(bytes);
                break;
            case ModbusConstants.RtuFunction.CODE_5:
                frameLength = frameLength5(bytes);
                break;
            default:
                log.warn("客户端消息：协议功能码有误， 功能码：{}", protocolIdentifier);
        }
        return frameLength;
    }

    /**
     * 校验 0x05: 写单个线圈寄存器
     * <p>
     * *  - max. 16 Byte (ADU = 4 Byte MBAP + 8 Byte + 4 Byte PDU)
     * <p>
     * <------------------------------------ ADU ------------------------------------->
     * <---------------------------- MBAP -----------><------------- PDU ------------->
     * +-----------------------+---------------------+----------------++--------------+
     * | Addresses  Identifier | Protocol Identifier | Data           | Check Bit    |
     * | (2 byte)              | (2 byte)            | (1 - 8 Byte) | (4  byte)    |
     * +----------------------+----------------------+----------------++-------------+
     */
    private int frameLength5(byte[] bytes) {
        /**
         * 解析得到数据长度  8 = 1 + 1 + 4 + 2
         *
         * +-----------------------+---------------------+----------------++--------------+
         * | Addresses  Identifier | Protocol Identifier | Data           | Check Bit    |
         * | (1 字节)              | (1  字节)            | (4字节)         | (2  字节)     |
         * +----------------------+---------------------+-----------------++-------------+
         */
        return 8;
    }

    /**
     * 校验 0x03: 读保持寄存器
     * <p>
     * *  - max. 262 Byte (ADU = 6 Byte MBAP + 252 Byte + 4 Byte PDU)
     * <p>
     * <----------------------------------------------- ADU ------------------------------------->
     * <---------------------------- MBAP ----------------------><------------- PDU ------------->
     * +-----------------------+---------------------+----------+----------------++--------------+
     * | Addresses  Identifier | Protocol Identifier | Length   | Data           | Check Bit    |
     * | (2 byte)              | (2 byte)            | (2 byte) | (1 - 252 Byte) | (4  byte)    |
     * +----------------------+---------------------+----------+-----------------++-------------+
     */
    private int frameLength3(byte[] bytes) {
        // 数据长度
        int dataLength = bytes[2];
        /**
         * 解析得到数据长度
         *
         * aduLength =  1          +          1          +      1   +    dataLength  +    2;
         * +-----------------------+---------------------+----------+----------------++--------------+
         * | Addresses  Identifier | Protocol Identifier | Length   | Data           | Check Bit    |
         * | (1 字节)              | (1  字节)            | (1  字节) | (1 - 252 Byte) | (2  字节)     |
         * +----------------------+---------------------+----------+-----------------++-------------+
         */
        return 5 + dataLength;
    }

    /**
     * crc16校验
     *
     * @param bytes 校验目标数组
     * @param aduLength 校验长度
     * @return
     *      0 - 校验错误
     *      1 - 长度不够
     *      2 - 正常
     */
    public int crc16(byte[] bytes, int aduLength) {
        // 目标数据长度
        int targetLength = bytes.length;
        // 目标数据长度 小于 解析得到数据长度  等待后续数据
        if (targetLength < aduLength) {
            log.warn("目标数据长度错误，目标数据长度小于解析得到数据长度，等待后续数据");
            return 1;
        }
        // 生成校验数据，需要去掉校验位两个字节
        int cdLength = aduLength - 2;
        byte[] cd = new byte[cdLength];
        if (cdLength >= 0) {
            System.arraycopy(bytes, 0, cd, 0, cdLength);
        }
        // CRC校验码
        byte[] crc16 = CRC16.crc16(cd);
        // 请求的crc16校验码
        int target = bytes[cdLength] + bytes[cdLength + 1];
        // 生成的crc16校验码
        int produce = 0;
        for (byte p: crc16) {
            produce = produce + p;
        }
        // 相等校验通过
        if (target == produce) {
            return 2;
        }
        log.info("客户端消息：crc16校验出错");
        return 0;
    }
}
